﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using System.Reflection;

namespace Pavilion
{
    public class ScriptXmlSerializer
    {
        public XDocument SerializeScript(NodeScript nodeScript)
        {
            XDocument xmlDocument = new XDocument();

            XElement scriptElement = new XElement("Script", new XAttribute("Type", nodeScript.NodeScriptType));
            xmlDocument.Add(scriptElement);

            scriptElement.Add(SerializeNode(nodeScript.RootContainer));

            return xmlDocument;
        }

        private XElement SerializeNode(NodeBase nodeBase)
        {
            XElement nodeElement = new XElement(nodeBase.GetType().Name);
            nodeElement.Add(new XAttribute("Name", nodeBase.Name));

            if (nodeBase is ContainerNode)
                SerializeContainerNode((ContainerNode)nodeBase, nodeElement);
            else if (nodeBase is AnimationNode)
                SerializeAnimationNode((AnimationNode)nodeBase, nodeElement);
            else if (nodeBase is LoadNode)
                SerializeLoadNode((LoadNode)nodeBase, nodeElement);

            if (nodeBase.GameDataCollection.Count != 0)
                nodeElement.Add(SerializeGameToolDataCollection(nodeBase.GameDataCollection));

            return nodeElement;
        }

        public void SerializeContainerNode(ContainerNode containerNode, XElement containerElement)
        {
            containerElement.Add(SerializeNode(containerNode.EntryNode));

            XElement nodeCollectionElement = new XElement("NodeCollection");
            containerElement.Add(nodeCollectionElement);

            foreach(NodeBase node in containerNode.NodeCollection.Where((x) => !(x is EntryNode)))
                nodeCollectionElement.Add(SerializeNode(node));

            containerElement.Add(SerializeLinkCollection(containerNode.LinkCollection));
        }

        private void SerializeAnimationNode(AnimationNode animationNode, XElement animationNodeElement)
        {
            if (animationNode.Animation != null)
                animationNodeElement.Add(new XAttribute("Animation", animationNode.Animation.Name));
            else
                animationNodeElement.Add(new XAttribute("Animation", ""));
        }

        private void SerializeLoadNode(LoadNode loadNode, XElement nodeElement)
        {
            XElement gameItemCollectionElement = new XElement("GameItemCollection");
            nodeElement.Add(gameItemCollectionElement);

            foreach (string gameItem in loadNode.GameItemCollection)
            {
                XElement gameItemElement = new XElement(gameItem);
                gameItemElement.Add(SerializeGameToolDataCollection(loadNode.ObtainGameTools(gameItem)));

                gameItemCollectionElement.Add(gameItemElement);
            }
        }

        private XElement SerializeGameToolDataCollection(IEnumerable<GameData> gameDataCollection)
        {
            XElement gameToolDataCollectionElement = new XElement("GameDataCollection");

            foreach (GameData gameData in gameDataCollection)
                gameToolDataCollectionElement.Add(SerializeGameToolData(gameData));

            return gameToolDataCollectionElement;
        }

        private XElement SerializeGameToolData(GameData gameData)
        {
            XElement gameToolDataElement = new XElement(gameData.GameToolName);

            foreach (string key in gameData.PropertyDictionary.Keys)
            {
                XElement gameDataElement = new XElement(key);
                gameDataElement.Add(new XAttribute("Data", GameEngine.GetStringRepresention(gameData.PropertyDictionary[key])));
                gameDataElement.Add(new XAttribute("Type", gameData.PropertyDictionary[key].GetType().FullName));

                gameToolDataElement.Add(gameDataElement);
            }

            return gameToolDataElement;
        }

        private IEnumerable<XElement> SerializeLinkCollection(IEnumerable<Link> linkList)
        {
            List<XElement> linkElementList = new List<XElement>();

            foreach (Link link in linkList)
                linkElementList.Add(SerializeLink(link));

            return linkElementList;
        }

        private XElement SerializeLink(Link link)
        {
            XElement linkElement = new XElement("Link");

            linkElement.Add(new XAttribute("OpenCriteria", link.OpenCriteria));
            linkElement.Add(new XAttribute("CurrentNode", link.StartingNode.Name));
            linkElement.Add(new XAttribute("NextNode", link.EndingNode.Name));

            linkElement.Add(SerializeLockCollection(link.LockCollection));

            return linkElement;
        }

        private IEnumerable<XElement> SerializeLockCollection(IEnumerable<ILock> lockList)
        {
            List<XElement> lockElementList = new List<XElement>();

            foreach (ILock lockItem in lockList)
            {
                XElement lockElement = lockItem.Serialize();
                lockElement.Add(new XAttribute("Type", lockItem.GetType().Name));
                lockElement.Add(new XAttribute("GameItemName", (lockItem.GameItemName == null) ? "" : lockItem.GameItemName));
                lockElementList.Add(lockElement);
            }

            return lockElementList;
        }

        public ContainerNode DeserializeContainerNode(XElement containerElement, string resourceName)
        {
            ContainerNode containerNode = new ContainerNode(containerElement.Attribute("Name").Value);

            XElement entryNodeElement = containerElement.Element("EntryNode");
            if (entryNodeElement != null)
                DeserializeGameDataCollection(entryNodeElement.Element("GameDataCollection"), containerNode.EntryNode);

            XElement nodeCollectionElement = containerElement.Element("NodeCollection");
            IEnumerable<XElement> containerElementList = nodeCollectionElement.Elements("ContainerNode");
            IEnumerable<XElement> animationElementList = nodeCollectionElement.Elements("AnimationNode");
            IEnumerable<XElement> defaultElementList = nodeCollectionElement.Elements("DefaultNode");
            IEnumerable<XElement> loadElementList = nodeCollectionElement.Elements("LoadNode");

            foreach (XElement containerChildElement in containerElementList)
                containerNode.AddNode(DeserializeContainerNode(containerChildElement, resourceName));

            foreach (XElement animationChildElement in animationElementList)
                containerNode.AddNode(DeserializeAnimationNode(animationChildElement, resourceName));

            foreach (XElement defaultChildElement in defaultElementList)
                containerNode.AddNode(DeserializeDefaultNode(defaultChildElement, resourceName));

            foreach (XElement loadChildElement in loadElementList)
                containerNode.AddNode(DeserializeLoadNode(loadChildElement, resourceName));

            DeserializeLinkCollection(containerElement, containerNode, resourceName);
            DeserializeGameDataCollection(containerElement.Element("GameDataCollection"), containerNode);

            return containerNode;
        }

        private DefaultNode DeserializeDefaultNode(XElement defaultNodeElement, string resourceName)
        {
           DefaultNode defaultNode = new DefaultNode(defaultNodeElement.Attribute("Name").Value);

           DeserializeGameDataCollection(defaultNodeElement.Element("GameDataCollection"), defaultNode);

           return defaultNode;
        }

        private LoadNode DeserializeLoadNode(XElement loadNodeElement, string resourceName)
        {
            LoadNode loadNode = new LoadNode(loadNodeElement.Attribute("Name").Value);
            DeserializeGameDataCollection(loadNodeElement.Element("GameDataCollection"), loadNode);

            XElement gameItemCollectionElement = loadNodeElement.Element("GameItemCollection");

            foreach (XElement gameItemElement in gameItemCollectionElement.Elements())
            {
                loadNode.AddGameItem(gameItemElement.Name.LocalName);
                XElement gameToolDataCollectionElement = gameItemElement.Element("GameDataCollection");

                foreach (XElement gameToolDataElement in gameToolDataCollectionElement.Elements())
                {
                    GameData gameData = DeserializeGameData(gameToolDataElement);
                    loadNode.AddGameTool(gameItemElement.Name.LocalName, gameToolDataElement.Name.LocalName, gameData);
                }
            }

            return loadNode;
        }

        private AnimationNode DeserializeAnimationNode(XElement animationNodeElement, string resourceName)
        {
            string animationNodeName = animationNodeElement.Attribute("Name").Value;
            string animationName = animationNodeElement.Attribute("Animation").Value;
            AnimationCollection animationCollection = ResourceManager.Instance[resourceName].ObtainResourceCollection<AnimationCollection>();

            AnimationNode animationNode = new AnimationNode(animationNodeName);

            if (animationCollection.Contains(animationName))
                animationNode.Animation = animationCollection[animationName];

            DeserializeGameDataCollection(animationNodeElement.Element("GameDataCollection"), animationNode);

            return animationNode;
        }

        private void DeserializeGameDataCollection(XElement gameToolDataCollectionElement, NodeBase node)
        {
            if (gameToolDataCollectionElement == null)
                return;

            foreach (XElement gameToolDataElement in gameToolDataCollectionElement.Elements())
                node.GameDataCollection.Add(DeserializeGameData(gameToolDataElement));
        }

        private GameData DeserializeGameData(XElement gameToolDataElement)
        {
            GameData createdGameData = new GameData();
            createdGameData.GameToolName = gameToolDataElement.Name.LocalName;

            foreach (XElement gameDataElement in gameToolDataElement.Elements())
                createdGameData.AddData(gameDataElement.Name.LocalName, GameEngine.ParseObject(gameDataElement.Attribute("Type").Value, gameDataElement.Attribute("Data").Value));

            return createdGameData;
        }

        private void DeserializeLinkCollection(XElement containerElement, ContainerNode containerNode, string resourceName)
        {
            IEnumerable<XElement> linkElementList = containerElement.Elements("Link");

            foreach (XElement linkElement in linkElementList)
                DeserializeLink(linkElement, containerNode, resourceName);
        }

        private void DeserializeLink(XElement linkElement, ContainerNode containerNode, string resourceName)
        {
            OpenCriteria openCriteria = (OpenCriteria)Enum.Parse(typeof(OpenCriteria), linkElement.Attribute("OpenCriteria").Value);
            string currentNodeName = linkElement.Attribute("CurrentNode").Value;
            string nextNodeName = linkElement.Attribute("NextNode").Value;
            IEnumerable<XElement> lockElementList = linkElement.Elements("Lock");

            IEnumerable<ILock> lockList = DeserializeLockCollection(lockElementList, resourceName);
            containerNode.LinkNode(currentNodeName, nextNodeName, lockList, openCriteria);
        }

        private IEnumerable<ILock> DeserializeLockCollection(IEnumerable<XElement> lockElementList, string resourceName)
        {
            List<ILock> lockList = new List<ILock>();

            foreach (XElement lockElement in lockElementList)
                lockList.Add(DeserializeLock(lockElement, resourceName));

            return lockList;
        }

        private ILock DeserializeLock(XElement lockElement, string resourceName)
        {
            string lockName = lockElement.Attribute("Type").Value;
            ILock createdLock = GameEngine.CreateLocalInstance<ILock>("Pavilion.Locks.dll", lockName);
            createdLock.Deserialize(lockElement, resourceName);

            createdLock.GameItemName = lockElement.Attribute("GameItemName").Value;

            return createdLock;
        }
    }
}
